/*
 * Filename	:Flash_Port.c
 * This file is the W25Q16 Driver
 *
 *          +-----------------------------------------------------------+
 *          |                     Pin assignment                        |
 *          +-----------------------------+---------------+-------------+
 *          |  STM32 SPI1 Pins            |  W25QXXX      |    Pin      |
 *          |oldversion:SPI3(before161220)|  SST25VFXXXB  |             |
 *          +-----------------------------+---------------+-------------+
 *          | sFLASH_CS_PIN/         PB6  | ChipSelect(/S)|    1        |
 *          | sFLASH_SPI_MISO_PIN /  PB4  |   DataOut(Q)  |    2        |
 *          |                             |  WP#(to VCC)  |    3 (3.3 V)|
 *          |                             |   GND         |    4 (0 V)  |
 *          | sFLASH_SPI_MOSI_PIN /  PB5  |   DataIn(D)   |    5        |
 *          | sFLASH_SPI_SCK_PIN /   PB3  |   Clock(C)    |    6        |
 *          |                             |  HOLD#(to VCC)|    7 (3.3 V)|
 *          |                             |    VCC        |    8 (3.3 V)|
 *          +-----------------------------+---------------+-------------+
 * Date		Author		Note
 * 2018.0403	guanjingxin	This first version
 *
 */
#include "gd32f30x_libopt.h"
#include "Flash_Port.h"

/*******************************************************************************
* Function Name  : spi_flash_init(void).
* Description    : spi_flash init .
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void spi_flash_init(void)
{
    spi_parameter_struct spi_init_struct;
    /*enable alternate function clock */
    gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP,ENABLE);
    rcu_periph_clock_enable(RCU_AF);
    rcu_periph_clock_enable(RCU_GPIOB);
    rcu_periph_clock_enable(RCU_SPI0);
    rcu_periph_clock_enable(RCU_SPI1);
    rcu_periph_clock_enable(RCU_SPI2);

     /* SPI1_SCK(PB3), SPI1_MISO(PB4) and SPI1_MOSI(PB5) GPI1 pin configuration */
    gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_3 |GPIO_PIN_5);
    gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_4);
    /* SPI1_CS(PB6) GPIO pin configuration */
    gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_6);           //spi cs
   
    /* chip select invalid*/
    SPI_FLASH_CS_HIGH();

    /* SPI0 parameter config */
    spi_init_struct.trans_mode           = SPI_TRANSMODE_FULLDUPLEX;
    spi_init_struct.device_mode          = SPI_MASTER;;
    spi_init_struct.frame_size           = SPI_FRAMESIZE_8BIT;;
    spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;      //guan
    spi_init_struct.nss                  = SPI_NSS_SOFT;
    spi_init_struct.prescale             = SPI_PSC_2;
    spi_init_struct.endian               = SPI_ENDIAN_MSB;;
    spi_init(SPI2, &spi_init_struct);
    
    /* set crc polynomial */
    spi_crc_polynomial_set(SPI2,7);
    /* enable SPI0 */
    spi_enable(SPI2);

}

/*******************************************************************************
* Function Name  : spi_flash_send_byte(void).
* Description    : spi write or read one byte.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
uint8_t spi_flash_send_byte(uint8_t byte)
{
    /* loop while data register in not emplty */
    while (RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_TBE));

    /* send byte through the SPI0 peripheral */
    spi_i2s_data_transmit(SPI2,byte);

    /* wait to receive a byte */
    while(RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_RBNE));

    /* return the byte read from the SPI bus */
    return(spi_i2s_data_receive(SPI2));
}

/*******************************************************************************
* Function Name  : spi_flash_write_enable(void).
* Description    : spi write enable.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void spi_flash_write_enable(void)
{
    /* select the flash: chip select low */
    SPI_FLASH_CS_LOW();

    /* send "write enable" instruction */
    spi_flash_send_byte(FLASH_CMD_WREN);

    /* deselect the flash: chip select high */
    SPI_FLASH_CS_HIGH();
}

/*******************************************************************************
* Function Name  : spi_flash_wait_for_write_end(void).
* Description    : spi_flash_wait_for_write_end.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void spi_flash_wait_for_write_end(void)
{
    uint8_t flash_status = 0;

    /* select the flash: chip select low */
    SPI_FLASH_CS_LOW();

    /* send "read status register" instruction */
    spi_flash_send_byte(FLASH_CMD_RDSR);

    /* loop as long as the memory is busy with a write cycle */
    do{
        /* send a dummy byte to generate the clock needed by the flash
        and put the value of the status register in flash_status variable */
        flash_status = spi_flash_send_byte(FLASH_DUMMY_BYTE);
    }while((flash_status & FLASH_WIP_FLAG) == SET);

    
    /* deselect the flash: chip select high */
    SPI_FLASH_CS_HIGH();
}

/*******************************************************************************
* Function Name  : spi_flash_sector_erase(void).
* Description    : spi erase sector.
* Input          : sertor address
* Output         : None
* Return         : None
*******************************************************************************/
void spi_flash_sector_erase(uint32_t sector_addr)
{
    /* send write enable instruction */
    spi_flash_write_enable();

    /* sector erase */
    /* select the flash: chip select low */
    SPI_FLASH_CS_LOW();
    /* send sector erase instruction */
    spi_flash_send_byte(FLASH_CMD_SECTOR_ERASE);
    /* send sector_addr high nibble address byte */
    spi_flash_send_byte((sector_addr & 0xFF0000) >> 16);
    /* send sector_addr medium nibble address byte */
    spi_flash_send_byte((sector_addr & 0xFF00) >> 8);
    /* send sector_addr low nibble address byte */
    spi_flash_send_byte(sector_addr & 0xFF);
    /* deselect the flash: chip select high */
    SPI_FLASH_CS_HIGH();

    /* wait the end of flash writing */
    spi_flash_wait_for_write_end();
}


/*!
    \brief      write more than one byte to the flash
    \param[in]  pbuffer: pointer to the buffer
    \param[in]  write_addr: flash's internal address to write
    \param[in]  num_byte_to_write: number of bytes to write to the flash
    \param[out] none
    \retval     none
*/
void spi_flash_page_write(uint8_t* pbuffer, uint32_t write_addr, uint16_t num_byte_to_write)
{
    /* enable the write access to the flash */
    spi_flash_write_enable();

    /* select the flash: chip select low */
    SPI_FLASH_CS_LOW();

    /* send "write to memory" instruction */
    spi_flash_send_byte(FLASH_CMD_PAGE_PROGRAM);
    /* send write_addr high nibble address byte to write to */
    spi_flash_send_byte((write_addr & 0xFF0000) >> 16);
    /* send write_addr medium nibble address byte to write to */
    spi_flash_send_byte((write_addr & 0xFF00) >> 8);
    /* send write_addr low nibble address byte to write to */
    spi_flash_send_byte(write_addr & 0xFF);

    /* while there is data to be written on the flash */
    while(num_byte_to_write--){
        /* send the current byte */
        spi_flash_send_byte(*pbuffer);
        /* point on the next byte to be written */
        pbuffer++;
    }

    /* deselect the flash: chip select high */
    SPI_FLASH_CS_HIGH();

    /* wait the end of flash writing */
    spi_flash_wait_for_write_end();
}

/*!
    \brief      write block of data to the flash
    \param[in]  pbuffer: pointer to the buffer
    \param[in]  write_addr: flash's internal address to write
    \param[in]  num_byte_to_write: number of bytes to write to the flash
    \param[out] none
    \retval     none
*/
void spi_flash_buffer_write(uint8_t* pbuffer, uint32_t write_addr, uint16_t num_byte_to_write)
{
    uint8_t num_of_page = 0, num_of_single = 0, addr = 0, count = 0, temp = 0;

    addr          = write_addr % FLASH_PAGESIZE;
    count         = FLASH_PAGESIZE - addr;
    num_of_page   = num_byte_to_write / FLASH_PAGESIZE;
    num_of_single = num_byte_to_write % FLASH_PAGESIZE;

     /* write_addr is FLASH_PAGESIZE aligned  */
    if(0 == addr){
        /* num_byte_to_write < FLASH_PAGESIZE */
        if(0 == num_of_page)
            spi_flash_page_write(pbuffer,write_addr,num_byte_to_write);
        /* num_byte_to_write > FLASH_PAGESIZE */
        else{
            while(num_of_page--){
                spi_flash_page_write(pbuffer,write_addr,FLASH_PAGESIZE);
                write_addr += FLASH_PAGESIZE;
                pbuffer += FLASH_PAGESIZE;
            }
            spi_flash_page_write(pbuffer,write_addr,num_of_single);
        }
    }else{
        /* write_addr is not FLASH_PAGESIZE aligned  */
        if(0 == num_of_page){
            /* (num_byte_to_write + write_addr) > FLASH_PAGESIZE */
            if(num_of_single > count){
                temp = num_of_single - count;
                spi_flash_page_write(pbuffer,write_addr,count);
                write_addr += count;
                pbuffer += count;
                spi_flash_page_write(pbuffer,write_addr,temp);
            }else
                spi_flash_page_write(pbuffer,write_addr,num_byte_to_write);
        }else{
            /* num_byte_to_write > FLASH_PAGESIZE */
            num_byte_to_write -= count;
            num_of_page = num_byte_to_write / FLASH_PAGESIZE;
            num_of_single = num_byte_to_write % FLASH_PAGESIZE;

            spi_flash_page_write(pbuffer,write_addr, count);
            write_addr += count;
            pbuffer += count;

            while(num_of_page--){
                spi_flash_page_write(pbuffer,write_addr,FLASH_PAGESIZE);
                write_addr += FLASH_PAGESIZE;
                pbuffer += FLASH_PAGESIZE;
            }

            if(0 != num_of_single)
                spi_flash_page_write(pbuffer,write_addr,num_of_single);
        }
    }
}

/*!
    \brief      read a block of data from the flash
    \param[in]  pbuffer: pointer to the buffer that receives the data read from the flash
    \param[in]  read_addr: flash's internal address to read from
    \param[in]  num_byte_to_read: number of bytes to read from the flash
    \param[out] none
    \retval     none
*/
void spi_flash_buffer_read(uint8_t* pbuffer, uint32_t read_addr, uint16_t num_byte_to_read)
{
    /* select the flash: chip slect low */
    SPI_FLASH_CS_LOW();

    /* send "read from memory " instruction */
    spi_flash_send_byte(FLASH_CMD_READ);

    /* send read_addr high nibble address byte to read from */
    spi_flash_send_byte((read_addr & 0xFF0000) >> 16);
    /* send read_addr medium nibble address byte to read from */
    spi_flash_send_byte((read_addr& 0xFF00) >> 8);
    /* send read_addr low nibble address byte to read from */
    spi_flash_send_byte(read_addr & 0xFF);

    /* while there is data to be read */
    while(num_byte_to_read--){
        /* read a byte from the flash */
        *pbuffer = spi_flash_send_byte(FLASH_DUMMY_BYTE);
        /* point to the next location where the byte read will be saved */
        pbuffer++;
    }

    /* deselect the flash: chip select high */
    SPI_FLASH_CS_HIGH();
}

/*!
    \brief      read flash identification
    \param[in]  none
    \param[out] none
    \retval     flash identification
*/
uint32_t spi_flash_read_id(void)
{
    uint32_t temp = 0, temp0 = 0, temp1 = 0, temp2 = 0;

    /* select the flash: chip select low */
    SPI_FLASH_CS_LOW();

    /* send "RDID " instruction */
    spi_flash_send_byte(0x9F);

    /* read a byte from the flash */
    temp0 = spi_flash_send_byte(FLASH_DUMMY_BYTE);

    /* read a byte from the flash */
    temp1 = spi_flash_send_byte(FLASH_DUMMY_BYTE);

    /* read a byte from the flash */
    temp2 = spi_flash_send_byte(FLASH_DUMMY_BYTE);

    /* deselect the flash: chip select high */
    SPI_FLASH_CS_HIGH();

    temp = (temp0 << 16) | (temp1 << 8) | temp2;

    return temp;
}

/*!
    \brief      initiate a read data byte (read) sequence from the flash
    \param[in]  read_addr: flash's internal address to read from
    \param[out] none
    \retval     none
*/
void spi_flash_start_read_sequence(uint32_t read_addr)
{
    /* select the flash: chip select low */
    SPI_FLASH_CS_LOW();

    /* send "read from memory " instruction */
    spi_flash_send_byte(FLASH_CMD_READ);

    /* send the 24-bit address of the address to read from */
    /* send read_addr high nibble address byte */
    spi_flash_send_byte((read_addr & 0xFF0000) >> 16);
    /* send read_addr medium nibble address byte */
    spi_flash_send_byte((read_addr& 0xFF00) >> 8);
    /* send read_addr low nibble address byte */
    spi_flash_send_byte(read_addr & 0xFF);
}

/*!
    \brief      read a byte from the SPI flash
    \param[in]  none
    \param[out] none
    \retval     byte read from the SPI flash
*/
uint8_t spi_flash_read_byte(void)
{
    return(spi_flash_send_byte(FLASH_DUMMY_BYTE));
}

/*!
    \brief      send a half word through the SPI interface and return the half word received from the SPI bus
    \param[in]  half_word: half word to send
    \param[out] none
    \retval     the value of the received byte
*/
uint16_t spi_flash_send_halfword(uint16_t half_word)
{
    /* loop while data register in not emplty */
    while(RESET == spi_i2s_flag_get(SPI0,SPI_FLAG_TBE));

    /* send half word through the SPI0 peripheral */
    spi_i2s_data_transmit(SPI0,half_word);

    /* wait to receive a half word */
    while(RESET == spi_i2s_flag_get(SPI0,SPI_FLAG_RBNE));

    /* return the half word read from the SPI bus */
    return spi_i2s_data_receive(SPI0);
}

